package edu.xidian.onlinedocument.websocket;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import edu.xidian.onlinedocument.handler.AttributeName;
import edu.xidian.onlinedocument.handler.excpetions.custom.CustomException;
import edu.xidian.onlinedocument.service.DocumentService;
import edu.xidian.onlinedocument.utils.SpringUtil;
import edu.xidian.onlinedocument.utils.ot.TextManagement;
import edu.xidian.onlinedocument.utils.ot.internal.operations.ChangesList;
import edu.xidian.onlinedocument.utils.ot.json.*;
import edu.xidian.onlinedocument.views.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.server.Session;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import javax.websocket.OnError;
import java.io.IOException;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
public class EditorWebSocketHandler extends TextWebSocketHandler implements WebSocketHandler {

    // 如果有@Slf4j注解，就不用手动创建logger对象
    // private final Logger logger = LoggerFactory.getLogger(EditorWebSocketHandler.class);

    /**
     * 新连接建立时。被调用
     * 对应着原生WebSocket的onOpen()方法，这里是spring框架包装过的
     *
     * 注意这里使用了一个静态类OperationSocketManager来管理连接用户列表
     * 注意这里用到了一个双键值的类OperationConnectKey作为Map的key，需要重写其hashCode方法，
     * 其实还有更好的方法，比如用google的HashBasedTable类
     * @param session websocket会话
     * @throws Exception 异常
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        EditorSocketKey key = this.getKey(session);
        if (EditorSocketManager.add(key.getUserId(), key.getDocId(), session)) {
            EditorSocketManager.join(key.getDocId());
            log.info("新增用户连接: [{}] ", key);
        }
        else {
            log.warn("新增用户连接失败: [{}] ", key);
        }
        // String text = EditorSocketManager.getTextManagement(key.getDocId()).getText().build();
        // this.sendDocumentsDataToEditors(text, session);
        // EditorSocketManager.printAllSession();

    }

    /**
     * 处理前端发送的文本信息
     * js调用websocket.send的时候，会调用该方法
     *
     * @param session 会话
     * @param message 文本信息
     * @throws Exception 异常
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        EditorSocketKey key = this.getKey(session);
        int docId = key.getDocId();
        String userNum = key.getUserNum();
        int userId = key.getUserId();
        log.info("接收到用户 [{}] 的消息: [{}] ", key, message.getPayload());
        ChangesList data = FromJSON.fromJSONToChangesList(JSONObject.parseObject(message.getPayload()));

        TextManagement textManagement = EditorSocketManager.getTextManagement(docId);
        // 将操作作用到Text上
        try {
            if (textManagement.receive(data)) {
                // 将操作存在数据库中
                textManagement.write();
                // 取得处理完后的操作，并逐个发送给所有的相关用户
                List<ChangesList> list = textManagement.send();
                for (ChangesList changesList:
                        list) {
                    String textMessage = ToJSON.fromChangesListToJSON(userNum, changesList).toString();
                    sendMessageToAllEditors(docId, new TextMessage(textMessage));
                }
                // 设置最新发送的版本号为最新发送的版本号
                // 注意不能直接调用text.getRevision()方法，因为两边可能是异步的
                textManagement.setLastSendRevision(list.get(list.size() - 1).getRevision());
            }
        } catch (Exception e) {
            handleCustomError(session, e);
        }
    }

    /**
     * 处理前端发送的文本或二进制信息
     * 对应着原生WebSocket的onMessage()方法
     *
     * @param session 会话
     * @param message 文本或者二进制信息
     * @throws Exception 异常
     */
    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
        // logger.info("handle message: {} ", message);
        // 处理客户端的ping，用于心跳机制
        if (message.getPayloadLength() == 0) {
            this.sendMessage(session, new TextMessage("alive"));
        }
        if (message.getPayloadLength() > 0) {
            handleTextMessage(session, (TextMessage) message);
        }
        // super.handleMessage(session, message);
    }

    /**
     * 当连接关闭时被调用
     * 对应着原生WebSocket的onClose()方法
     *
     * @param session 会话
     * @param status 关闭状态
     * @throws Exception 异常
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        EditorSocketKey key = this.getKey(session);
        if (EditorSocketManager.remove(key.getUserId(), key.getDocId())) {
            EditorSocketManager.quit(key.getDocId());
            log.info("移除用户连接: [{}] ", key);
        }
        else {
            log.warn("移除用户连接失败: [{}] ", key);
        }
        // EditorSocketManager.printAllSession();

    }

    /**
     * 传输发生错误时调用
     *
     * @param session 会话
     * @param exception 异常
     * @throws Exception 异常
     */
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        // EditorSocketKey key = this.getKey(session);
        session.sendMessage(new TextMessage(exception.getMessage()));
        // EditorSocketManager.remove(key.getUserId(), key.getDocId());
        if(session.isOpen()) {
            session.close();
        }
        log.error("连接出错: [{}]", exception.getMessage(), exception);
    }

    @OnError
    public void handleCustomError(WebSocketSession session, Throwable exception) throws Exception {
        session.sendMessage(new TextMessage(exception.getMessage()));
        log.warn("业务处理出错: [{}]", exception.getMessage(), exception);
    }

    @Override
    public boolean supportsPartialMessages() {
        return super.supportsPartialMessages();
    }


    /**
     * 获取会话中包含的key值，包装为OperationConnectKey类
     * @param session 会话
     * @return OperationConnectKey
     * @throws Exception 异常
     */
    private EditorSocketKey getKey(WebSocketSession session){
        try{
            Integer userId =  Integer.parseInt(session.getAttributes().get(AttributeName.CLIENT_ID).toString());
            Integer docId = Integer.parseInt(session.getAttributes().get(AttributeName.DOC_ID).toString());
            String userNum = String.valueOf(session.getAttributes().get(AttributeName.CLIENT_NUM));
            return new EditorSocketKey(userId, docId, userNum);
        } catch (Exception e) {
            // throw new CustomException(ResultCode.FAILED);
            return null;
        }
    }

    /*
    private void sendMessage(String jsonData) {
        logger.info("收到客户端消息，返回message {} ", jsonData);
        JSONObject json = JSONObject.parseObject(jsonData);
    }
     */

    private boolean sendMessage(WebSocketSession session, TextMessage message) throws IOException {
        if (session == null) {
            return false;
        }
        if (message == null) {
            return false;
        }
        session.sendMessage(message);
        return true;
    }

    private boolean sendMessageToOneEditor(EditorSocketKey key, TextMessage message) {
        if(key == null) {
            return false;
        }
        WebSocketSession session = EditorSocketManager.get(key.getUserId(), key.getDocId());
        log.info("发送给用户和文档 [{}], 以及消息 [{}] ", session, message);
        if(!session.isOpen()) {
            return false;
        }
        try {
            session.sendMessage(message);
        } catch (IOException e) {
            return false;
        }
        return true;
    }

    private boolean sendMessageToAllEditors(Integer docId, TextMessage message) {
        if(docId == null) {
            return false;
        }
        Map<Integer, WebSocketSession> sessions = EditorSocketManager.getSameDoc(docId);
        log.info("发送给用户和文档 [{}], 以及消息 [{}] ", sessions, message);
        try {
            for (WebSocketSession session:
                 sessions.values()) {
                if(session.isOpen()) {
                    session.sendMessage(message);
                }
            }
        } catch (IOException e) {
            log.error("发送消息失败 [{}]", message);
            return false;
        }
        return true;
    }

    /*
    private boolean sendDocumentsDataToEditors(String text, WebSocketSession session) throws IOException {
        if(text == null || session == null) {
            return false;
        }
        logger.info("sendDocumentsDataToEditors {} ", text);
        session.sendMessage(new TextMessage(text));
        return true;
    }
     */
}
